﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using Common.Threading;

namespace Common.Linq
{
    public class ThreadSafeQueryable<TEntity> : IQueryable<TEntity>
    {
        private IQueryable<TEntity> m_InnerQueryable;
        private ReaderWriterLockSlim m_ReaderWriterSync;
        private Object m_PrimitiveSync;
        private ThreadSafeQueryProvider m_Provider;

        public ThreadSafeQueryable(IQueryable<TEntity> innerQueryable, ReaderWriterLockSlim sync)
        {
            this.m_InnerQueryable = innerQueryable;
            this.m_ReaderWriterSync = sync;
            this.m_Provider = new ThreadSafeQueryProvider(this.m_InnerQueryable.Provider, sync);
        }

        public ThreadSafeQueryable(IQueryable<TEntity> innerQueryable, Object sync)
        {
            this.m_InnerQueryable = innerQueryable;
            this.m_PrimitiveSync = sync;
            this.m_Provider = new ThreadSafeQueryProvider(this.m_InnerQueryable.Provider, sync);
        }

        public Type ElementType
        {
            get { return this.m_InnerQueryable.ElementType; }
        }

        public Expression Expression
        {
            get { return this.m_InnerQueryable.Expression; }
        }

        public IQueryProvider Provider
        {
            get { return this.m_Provider; }
        }

        #region IEnumerable<TEntity> Members

        public IEnumerator<TEntity> GetEnumerator()
        {
            if (this.m_ReaderWriterSync != null)
            {
                using (ReaderLock rLock = new ReaderLock(this.m_ReaderWriterSync))
                {
                    return this.m_InnerQueryable.GetEnumerator();
                }
            }
            else
            {
                lock (this.m_PrimitiveSync)
                {
                    return this.m_InnerQueryable.GetEnumerator();
                }
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        private class ThreadSafeQueryProvider : IQueryProvider
        {
            private ReaderWriterLockSlim m_ReaderWriterSync;
            private Object m_PrimitiveSync;
            private IQueryProvider m_InnerProvider;
            private MethodInfo m_CreateQueryMethod = typeof(ThreadSafeQueryProvider).GetMethod("CreateQuery<>");

            public ThreadSafeQueryProvider(IQueryProvider provider, ReaderWriterLockSlim sync)
            {
                this.m_InnerProvider = provider;
                this.m_ReaderWriterSync = sync;
            }

            public ThreadSafeQueryProvider(IQueryProvider provider, Object sync)
            {
                this.m_InnerProvider = provider;
                this.m_PrimitiveSync = sync;
            }

            public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
            {
                if (this.m_ReaderWriterSync != null)
                {
                    return new ThreadSafeQueryable<TElement>(this.m_InnerProvider.CreateQuery<TElement>(expression), this.m_ReaderWriterSync);
                }
                else
                {
                    return new ThreadSafeQueryable<TElement>(this.m_InnerProvider.CreateQuery<TElement>(expression), this.m_PrimitiveSync);
                }
            }

            public IQueryable CreateQuery(Expression expression)
            {
                return (IQueryable)this.m_CreateQueryMethod.MakeGenericMethod(expression.Type).Invoke(this, new Object[] { expression });
            }

            public TResult Execute<TResult>(Expression expression)
            {
                if (this.m_ReaderWriterSync != null)
                {
                    using (ReaderLock rLock = new ReaderLock(this.m_ReaderWriterSync))
                    {
                        return this.m_InnerProvider.Execute<TResult>(expression);
                    }
                }
                else
                {
                    lock (this.m_PrimitiveSync)
                    {
                        return this.m_InnerProvider.Execute<TResult>(expression);
                    }
                }
            }

            public Object Execute(Expression expression)
            {
                if (this.m_ReaderWriterSync != null)
                {
                    using (ReaderLock rLock = new ReaderLock(this.m_ReaderWriterSync))
                    {
                        return this.m_InnerProvider.Execute(expression);
                    }
                }
                else
                {
                    lock (this.m_PrimitiveSync)
                    {
                        return this.m_InnerProvider.Execute(expression);
                    }
                }
            }
        }
    }
}
